// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: leon

#pragma once

#include <memory>
#include <optional>
#include <string>
#include <vector>

#include "coin2/exchange/base/symbology/spot_product.h"
#include "coin2/exchange/base/symbology/util.h"
#include "coin2/exchange/coingecko/symbology/currency.h"
#include "coin2/exchange/base/symbology/product_encyclopedia.h"

namespace coin2::exchange::coingecko::symbology {

class CoingeckoProduct : public base::symbology::SpotProduct<CoingeckoCurrency, CoingeckoProduct>,
                         public base::symbology::ProductFromStrMixin<CoingeckoProduct> {
 public:
  using BaseProduct = SpotProduct<CoingeckoCurrency, CoingeckoProduct>;

  CoingeckoProduct(const CoingeckoCurrency& base, const CoingeckoCurrency& quote)
      : SpotProduct(
            base,
            quote,
            ToCoingeckoNativeProduct(base, quote),
            ToCoingeckoNativeProduct(base, quote),
            base::market::ExchangeType::Coingecko) {}

  static std::unique_ptr<CoingeckoProduct> FromNativeStrImpl(
      const std::string& native_product,
      int64_t resolve_timestamp) {
    std::unique_ptr<CoingeckoCurrency> base_ptr;
    std::unique_ptr<CoingeckoCurrency> quote_ptr;
    bool success = ParseCoingeckoNativeProduct(native_product, resolve_timestamp, &base_ptr, &quote_ptr);
    if (success) {
      return std::make_unique<CoingeckoProduct>(*base_ptr, *quote_ptr);
    } else {
      return {};
    }
  }

  static void InitProductInfoConstant(int64_t timestamp) {
    using ExchangeType = base::market::ExchangeType;
    using MarketType = base::market::MarketType;
    using ProductEncyclopedia = coin2::exchange::base::symbology::ProductEncyclopedia;

    const auto* pic =
        ProductEncyclopedia::GetProductHolderManager()
            ->GetMutableProductInfoConstantsManager()
            ->GetProductInfoConstant(MarketType::Spot, ExchangeType::Coingecko, timestamp);
    pic_ = const_cast<base::symbology::ProductInfoConstant*>(pic);
  }
  
 private:
  static base::symbology::ProductInfoConstant* pic_;
  static base::symbology::ProductInfoConstant* GetProductInfoConstant();

  static bool ParseCoingeckoNativeProduct(
      const std::string& product_str,
      int64_t resolve_timestamp,
      std::unique_ptr<CoingeckoCurrency>* base_ptr,
      std::unique_ptr<CoingeckoCurrency>* quote_ptr) {
    using base::market::MarketType;
    auto pic = GetProductInfoConstant();
    if (!pic->HasNativeProduct(product_str)) {
      return false;
    }
    auto pip = pic->GetProductInfoByNativeSymbol(product_str);
    std::string symbol = pip.symbol();
    std::vector<std::string> splitted;
    splitted = Split(symbol, "-");
    if (splitted.size() != 2) {
      return false;
    }
    *base_ptr = CoingeckoCurrency::FromNativeStrNoThrow(splitted[0], resolve_timestamp);
    *quote_ptr = CoingeckoCurrency::FromNativeStrNoThrow(splitted[1], resolve_timestamp);
    return true;
  }

  static std::string ToCoingeckoNativeProduct(
      const CoingeckoCurrency& base,
      const CoingeckoCurrency& quote) {
    using base::market::MarketType;
    std::stringstream ss;
    auto pic = GetProductInfoConstant();
    ss << base.currency() << '-' << quote.currency();
    auto pip = pic->GetProductInfo(ss.str());
    auto native_product_str = pip.native_symbol();
    return native_product_str;
  }
};

}  // namespace coin2::exchange::coingecko::symbology
